﻿using System;
using System.Diagnostics;
using System.Linq.Expressions;

namespace ExpressionMapper
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, ParameterExpression, Expression>;

    public static partial class Generators
    {
        /// <summary>
        /// Returns generator for mappings T to T?.
        /// </summary>
        public static MappingExpressionGenerator ToNullable()
        {
            return (fromExpr, to, customMapping, instanceDictionary) => (CanConvertToNullable(fromExpr.Type, to))
                                                        ? customMapping ??
                                                          GenrateToNullableExpression(fromExpr, to)
                                                        : null;
        }

        /// <summary>
        /// Returns generator for mappings T? to T.
        /// </summary>
        public static MappingExpressionGenerator FromNullable()
        {
            return (fromExpr, to, customMapping, instanceDictionary) =>
                   (CanConvertFromNullable(fromExpr.Type, to))
                       ? customMapping ??
                         GenerateFromNullableExpression(fromExpr, to)
                       : null;
        }

        private static Expression GenrateToNullableExpression(Expression fromExpr, Type to)
        {
            Debug.WriteLine(string.Format("\tToNullable ({0}): {1} to {2}", fromExpr.NodeType, fromExpr.Type.Name, to.Name));

            return Expression.Convert(fromExpr, to);
        }

        private static Expression GenerateFromNullableExpression(Expression fromExpr, Type to)
        {
            Debug.WriteLine(string.Format("\tFromNullable ({0}): {1} to {2}", fromExpr.NodeType,
                                  fromExpr.Type.Name,
                                  to.Name));

            return Expression.Condition(Expression.Equal(fromExpr, Expression.Constant(null)),
                Expression.Default(to)
                , Expression.MakeMemberAccess(fromExpr, fromExpr.Type.GetProperty("Value")))
            ;
        }

        public static bool IsNullable(Type type)
        {
            return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
        }

        private static bool CanConvertToNullable(Type from, Type to)
        {
            return from.IsValueType
                   && !IsNullable(from)
                   && (to == typeof(Nullable<>).MakeGenericType(from));
        }

        private static bool CanConvertFromNullable(Type from, Type to)
        {
            return to.IsValueType
                   && !IsNullable(to)
                   && (from == typeof(Nullable<>).MakeGenericType(to));
        }
    }
}